//
// Created by Administrator on 2016/12/9.
//

#include "Rubik.h"

using namespace std;
using namespace glm;
using namespace ZainGL;

uint16_t Rubik::Dimen=3;

default_random_engine Rubik::DefaultRandomEngine(time(0));

Rubik::~Rubik() {
    while (!mCubes.empty()) {
        Cube *&cube = mCubes.back();
        delete (cube);
        cube = nullptr;
        mCubes.pop_back();
    }
    Cube::instanceTransforms.clear();
}

Rubik::Rubik(btDynamicsWorld *dynamicsWorld, btCollisionShape *boxCollisionShape, float size, Model *model) {
    mat4 originTransform = model->getTransform();

    Cube::instanceTransforms.reserve(Rubik::Dimen * Rubik::Dimen * Rubik::Dimen + 1);
    for (int8_t x = 0; x < Rubik::Dimen; ++x) {
        for (int8_t y = 0; y < Rubik::Dimen; ++y) {
            for (int8_t z = 0; z < Rubik::Dimen; ++z) {
                if (x > 0 && x < Rubik::Dimen - 1 && y > 0 && y < Rubik::Dimen - 1 && z > 0 && z < Rubik::Dimen - 1) {
                    continue;
                }

                AXESPOS cubeAxes{x, y, z};
                Cube *cube = new Cube(dynamicsWorld, size, boxCollisionShape, originTransform, cubeAxes);
                mCubes.push_back(cube);
            }
        }
    }

    mCenter = vec3((Rubik::Dimen - 1) * size / 2);
}

void Rubik::onTick(float deltaTime) {
    if (autoRotateSliceRadian!=0){
        float32_t radian=autoRotateSpeed*deltaTime;
        if (fabs(autoRotateSliceRadian)>=radian){
            radian=autoRotateSliceRadian > 0 ? radian : -radian;
            rotateSlice(radian);
            autoRotateSliceRadian-=radian;
        } else{
            rotateSlice(autoRotateSliceRadian, true);

            /*autorotating done;*/
            mManualRotatedRadian=autoRotateSliceRadian=0;
            mRotateVector=vec3(-1);
            mClickedFace = NONE;
            mSameSliceCubes.clear();
            if (!bAutoMessing){
                bWin=checkWin();
            }
        }
    } else{
        if (mAutoMessCount-->0){
            if (!bAutoMessing){
                bAutoMessing= true;
            }
            mSameSliceCubes.clear();
            /*get base cube*/
            uniform_int_distribution<> u(0, mCubes.size()-1);
            Cube *baseCube = mCubes[u(DefaultRandomEngine)];

            /*get rotate axes pivot*/
            uniform_int_distribution<> u2(0, 2);
            AXES rotateAxesPivot = AXES(u2(DefaultRandomEngine));

            findSameSliceCubes(baseCube, rotateAxesPivot);

            autoRotateSliceRadian=PId2;
        } else{
            if (bAutoMessing){
                bAutoMessing= false;
            }
        }
    }
}

void Rubik::rotateSlice(float radian, bool bFinalRotation) {
    mManualRotatedRadian += radian;
    mat4 transBySliceCenter;
    transBySliceCenter = translate(transBySliceCenter, mSliceCenter);
    transBySliceCenter = rotate(transBySliceCenter, radian, mRotateVector);
    transBySliceCenter = translate(transBySliceCenter, -mSliceCenter);

    for (int i = 0; i < mSameSliceCubes.size(); ++i) {
        Cube *cube = mSameSliceCubes[i];

        auto newTrans = transBySliceCenter *
                        Cube::instanceTransforms[cube->mIndex]; //rotate same slice cubes by mRotateVector

        cube->resetTransform(newTrans, bFinalRotation?&mTransByMCenter: nullptr);
    }
}

void Rubik::findSameSliceCubes(Cube *baseCube, AXES rotateAxesPivot) {
    vec3 rubikRight, rubikUp, rubikForward;
    rubikRight = mTransByMCenter[0];
    rubikUp = mTransByMCenter[1];
    rubikForward = mTransByMCenter[2];

    auto &cubes = mCubes;
    uint16_t cubesNum = cubes.size();
    for (uint16_t i = 0; i < cubesNum; ++i) {
        AXESPOS curCubeAxesPos = cubes[i]->mAxesPos;
        switch (rotateAxesPivot) {
            case X:
                mRotateVector = rubikRight;
                if (mClickedFace == A1) {
                    mRotateVector = -mRotateVector;
                }

                /*determine slice center*/
                /*vec3 baseCubeCenter = Cube::instanceTransforms[clickedCube->mIndex][3];//clickedcube center now
                baseCubeCenter = glm::inverse(mTransByMCenter) * vec4(baseCubeCenter, 1.0f);//convert clickedcube center back to origin*/
                mSliceCenter = vec3(baseCube->mPosOrigin.x, mCenter.y, mCenter.z);
                mSliceCenter = mTransByMCenter * vec4(mSliceCenter, 1.0f);

                if (curCubeAxesPos.X == baseCube->mAxesPos.X) {
                    mSameSliceCubes.push_back(cubes[i]);
                }
                break;
            case Y:
                mRotateVector = rubikUp;

                mSliceCenter = vec3(mCenter.x, baseCube->mPosOrigin.y, mCenter.z);
                mSliceCenter = mTransByMCenter * vec4(mSliceCenter, 1.0f);

                if (curCubeAxesPos.Y == baseCube->mAxesPos.Y) {
                    mSameSliceCubes.push_back(cubes[i]);
                }
                break;
            case Z:
                mRotateVector = rubikForward;
                if (mClickedFace == B || mClickedFace == C) {
                    mRotateVector = -mRotateVector;
                }

                mSliceCenter = vec3(mCenter.x, mCenter.y, baseCube->mPosOrigin.z);
                mSliceCenter = mTransByMCenter * vec4(mSliceCenter, 1.0f);

                if (curCubeAxesPos.Z == baseCube->mAxesPos.Z) {
                    mSameSliceCubes.push_back(cubes[i]);
                }
                break;
        }
    }
}

bool Rubik::checkWin() {
    bool win = true;
    for_each(mCubes.cbegin(), mCubes.cend(), [&win](Cube *cube) {
        if (cube->mAxesPos != cube->mAxesPosOrigin) {
            win = false;
            return;
        }
    });
    return win;
}
